---@author: PC My 2025-08-10 10:35:36
---@class CaptureZone
CaptureZone = {}

---@class version
local version = {
    MAJOR = 0,
    MINOR = 0,
    PATCH = 2,
    Special = 'alpha',
}

do
---Config.
    local DebugMode = true

---Private attributes.
    ---@class zonePrivates
    ---@field name string Zone name.
    ---@field zoneType string Zone type.
    ---@field coalition number Zone coalition.
    ---@field state CaptureZone.ZONE_STATE Zone state.
    ---@field center Vec3 Zone center point.
    ---@field radius Vec3|nil Radius for a circle zone, for polygon its nil.
    ---@field verticies Vec3|nil Verticies for a polygon zone, for circle its nil.
    ---@field drawZoneID number Draw zone markup id.
    ---@field drawZoneTextID number Draw zone markup text id.
    
    ---@type table<CaptureZone,zonePrivates>
    local Privates = setmetatable({}, {__mode = "k"})  -- Weak reference table to avoid memory leaks.

    ---@type table<string,CaptureZone>
    local AllZones = {}

---Public attributes.
    ---@class CaptureZone.ZONE_TYPE
    CaptureZone.ZONE_TYPE = {
        circle = 'circle',
        polygon = 'polygon',
    }

    ---@class CaptureZone.ZONE_STATE
    CaptureZone.ZONE_STATE = {
        captured = 'captured',
        seizing = 'seizing',
    }

    ---@class ZoneCoalitionColor Coalition color configuration item
    ---@field name string Coalition name (such as 'neutual','red')
    ---@field line number[] Line color RGBA value (such as {1,1,1,0.5})
    ---@field fill number[] Fill color RGBA value (such as {1,1,1,0.3})
    ---@field flare trigger.flareColor Flare color
    ---@field smoke trigger.smokeColor|number Smoke color (supports enumeration or number)

    ---@type table<coalition.side,ZoneCoalitionColor>
    CaptureZone.ZONE_COALITION_COLOR = {
        [coalition.side.NEUTRAL] = {
            name = 'neutual',
            line = {1,1,1,0.5},
            fill = {1,1,1,0.3},
            flare = trigger.flareColor.White,
            smoke = trigger.smokeColor.White,
        },
        [coalition.side.RED] = {
            name = 'red',
            line = {1,0,0,0.5},
            fill = {1,0,0,0.3},
            flare = trigger.flareColor.Red,
            smoke = trigger.smokeColor.Red,
        },
        [coalition.side.BLUE] = {
            name = 'blue',
            line = {0,0,1,0.5},
            fill = {0,0,1,0.3},
            flare = trigger.flareColor.Green,
            smoke = trigger.smokeColor.Blue or 4,
        },
    }

    ---@class ZoneTextColor
    ---@field color number[] The color of the outline in { r, g, b, a } format values 0-1.
    ---@field fillColor number[] The color of the outline in { r, g, b, a } format values 0-1.
    ---@field fontSize number Size of the font to be used.

    ---@class CaptureZone.ZONE_TEXT_TYPE
    CaptureZone.ZONE_TEXT_TYPE = {
        Info = 0,
    }

    ---@type table<CaptureZone.ZONE_TEXT_TYPE,ZoneTextColor>
    CaptureZone.ZONE_TEXT_COLOR = {
        [CaptureZone.ZONE_TEXT_TYPE.Info] = {
            color = {0,0,0,1},
            fillColor = {1,1,1,0.3},
            fontSize = 14,
        },
    }

    CaptureZone.ZONE_LINE_TYPES = {
        NO_LINE = 0,
        SOLID = 1,
        DASHED = 2,
        DOTTED = 3,
        DOT_DASH = 4,
        LONG_DASH = 5,
        TWO_DASH = 6
    }


---Static functions & Private methods.
    local function Debug(text,title,forceShow,displayTime,clearview)
        if not (DebugMode or forceShow) then return end
        title = title or '[CaptureZone debug] '
        Utils.Debug(text,displayTime,clearview,title)
    end

    ---@param obj CaptureZone
    ---@return zonePrivates|nil
    local function getPrivates(obj)
        if not obj then return end
        return Privates[obj]
    end

    local function getZoneInitText(obj)
        if not obj then return end
        
        local privates = getPrivates(obj)
        if not privates then return end

        local text = privates.name

        if DebugMode then
            local coalitionNames = {
                [coalition.side.NEUTRAL] = 'NEUTRAL',
                [coalition.side.BLUE] = 'BLUE',
                [coalition.side.BLUE] = 'BLUE',
            }
            text = text..'\n'..coalitionNames[privates.coalition]
            text = text..'\n'..privates.state
            text = text..'\n'..privates.zoneType
        end

        return text
    end

    ---@param obj CaptureZone
    ---@param forCoalition coalition.side|number -1 for all.
    local function DrawZone(obj,forCoalition)
        if not obj then return end
        
        local privates = getPrivates(obj)
        if not privates then return end

        forCoalition = forCoalition or -1
        local color = CaptureZone.ZONE_COALITION_COLOR[privates.coalition]

        ---Draw zone.
        -- If zone has been drawed.
        local drawZoneID = privates.drawZoneID
        if drawZoneID then
            --Draw zone
            obj:setZoneColor(color.line,color.fill)
        end
        -- If zone has not been drawed.
        if not drawZoneID then
            -- Get markup id.
            drawZoneID = Utils.getMarkupID()
            -- Draw circle zone if its a circle.
            if privates.zoneType == CaptureZone.ZONE_TYPE.circle then
                trigger.action.circleToAll(forCoalition,drawZoneID,privates.center,privates.radius,color.line,color.fill,CaptureZone.ZONE_LINE_TYPES.SOLID)
            end
            -- Draw polygon zone if its a polygon.
            if privates.zoneType == CaptureZone.ZONE_TYPE.polygon then
                Utils.drawPolygon(forCoalition,drawZoneID,privates.verticies,color.line,color.fill,CaptureZone.ZONE_LINE_TYPES.SOLID)
            end
            privates.drawZoneID = drawZoneID
        end
        ---Draw zone text.
        -- If zone text has been drawed.
        local drawZoneTextID = privates.drawZoneTextID
        if drawZoneTextID then
            --Draw zone text
            obj:setZoneText()
        end
        -- If zone text has not been drawed.
        if not drawZoneTextID then
            -- Get markup id.
            local drawZoneTextID = Utils.getMarkupID()

            local text = getZoneInitText(obj)
            local textPoint = {x=privates.center.x,y=privates.center.y,z=privates.center.z+privates.radius}
            local textColor = CaptureZone.ZONE_TEXT_COLOR[CaptureZone.ZONE_TEXT_TYPE.Info]

            trigger.action.textToAll(forCoalition,drawZoneTextID,textPoint,textColor.color,textColor.fillColor,textColor.fontSize,false,text)
            privates.drawZoneTextID = drawZoneTextID
        end

        return true
    end
    
    ---@param obj CaptureZone
    ---@param tCoalition coalition.side
    local function setCapture(obj,tCoalition)
        if not obj then return end
        if not tCoalition then return end

        local privates = getPrivates(obj)
        privates.coalition = tCoalition
        privates.state = CaptureZone.ZONE_STATE.captured

        DrawZone(obj)
    end

    ---@param obj CaptureZone
    local function InitZone(obj)
        setCapture(obj,coalition.side.NEUTRAL)
        -- __Start(obj)
    end

---Construct methods.
    ---@param zoneName string; _name of a trigger zone.
    ---@return CaptureZone
    function CaptureZone:New(zoneName)
        local inputZone = Utils.getZone(zoneName)
        if not inputZone then
            Debug('Invalid input for new captureZone.',nil,true)
            Debug(zoneName,'[zoneName] ',true)
            return
        end
        if not inputZone.point or not (inputZone.radius or inputZone.verticies or Utils.getTableSize(inputZone.verticies)<3) then
            Debug('Invalid input for new captureZone.',nil,true)
            Debug(zoneName,'[zoneName] ',true)
            Debug(inputZone,'[inputZone] ',true)
            return
        end

        -- Check zone type: a circle or a polygon.
        local zoneType = CaptureZone.ZONE_TYPE.circle
        if inputZone.verticies then
            zoneType = CaptureZone.ZONE_TYPE.polygon
        end

        local obj = {}

        setmetatable(obj, self)
        self.__index = self

        local privates = {}
        privates.name = zoneName
        privates.zoneType = zoneType

        privates.center = Utils.deepCopy(inputZone.point)
        privates.radius = inputZone.radius
        privates.verticies = Utils.deepCopy(inputZone.verticies)

        AllZones[privates.name] = obj
        Privates[obj] = privates
        
        InitZone(obj)
        return obj
    end

---Piblic methods.
    ---@param zoneName string @Zone name.
    ---@return CaptureZone
    function CaptureZone.getZoneByName(zoneName)
        if not zoneName or type(zoneName)~='string' then return end

        return AllZones[zoneName]
    end

    ---@return string; Zone name.
    function CaptureZone:getName()
        local privates = getPrivates(self)
        return privates.name
    end

    ---@return string; Zone type name.
    function CaptureZone:getZoneType()
        local privates = getPrivates(self)
        return privates.zoneType
    end

    ---@return Vec3; Zone center.
    function CaptureZone:getZoneCenter()
        local privates = getPrivates(self)
        return Utils.deepCopy(privates.center)
    end

    ---@return number; Zone coalition.
    function CaptureZone:getZoneCoalition()
        local privates = getPrivates(self)
        return privates.coalition
    end

    ---@return CaptureZone.ZONE_STATE; Zone state.
    function CaptureZone:getZoneState()
        local privates = getPrivates(self)
        return privates.state
    end

    function CaptureZone:setZoneColor(color,fill,line)
        local privates = getPrivates(self)
        if not privates then return end
        if not privates.drawZoneID then return end

        if type(color)~='table' or Utils.getTableSize(color)<4 then 
            Debug('Invalid color for setZoneColor()')
            return 
        end
        if type(fill)~='table' or Utils.getTableSize(fill)<4 then 
            Debug('Invalid fill color for setZoneColor()')
            return 
        end

        local lineTypeMatch = false
        for type,number in pairs(CaptureZone.ZONE_LINE_TYPES) do
            if line == number then lineTypeMatch = true end
        end
        if not lineTypeMatch then 
            Debug('Invalid line type for setZoneColor()')
            return
        end

        if color then
            trigger.action.setMarkupColor(privates.drawZoneID,color)
        end

        if fill then
            trigger.action.setMarkupColorFill(privates.drawZoneID,fill)
        end

        if line then
            trigger.action.setMarkupTypeLine(privates.drawZoneID,line)
        end
    end

    function CaptureZone:setZoneText(newText,fontSize)
        local privates = getPrivates(self)
        if not privates then return end
        if not privates.drawZoneTextID then return end
        
        newText = newText or getZoneInitText(self)

        if newText then
            Debug('Set new zone text')
            trigger.action.setMarkupText(privates.drawZoneTextID,newText)
        end

        if fontSize then
            trigger.action.setMarkupFontSize(privates.drawZoneTextID,fontSize)
        end
    end

---Hide error message box.
    env.setErrorMessageBoxEnabled(DebugMode)

end

-- This will be shown if the module is successfully loaded.
trigger.action.outText('JetSpawner by Buta: version: '..version.MAJOR..'.'..version.MINOR..'.'..version.PATCH..version.Special,1)